; 16-bit DTC version - fastest yet
; opcodes are 16-bit addresses.  this doesn't affect the stack width which is still 8-bit.
; callasm has been eliminated.  all calls are the same.  any asm routine can be a bytecode subroutine.
; literals are slower, but, actual usage stats prove that they are less frequent than callasm.
; very important: there is a 2-byte alignment requirement for dtc.  use align 2 where appropriate (it's included in
;  vmnext and vmnext_pla)

; the approach from now on is to make this the primary coding environment and
;  have an alternate 8-bit interpreter that supports a subset of functionality (and built on much of the main VM)
;  and whose purpose is primarily to conserve space over speed.  (map data, simple scripts)

; because branch and nobranch pop the stack, they can be used to implement WHILE and UNTIL. (and REPEAT and AGAIN with an extra pha)


;Virtual Machine Register descriptions and implementation
;data stack     6502 stack
;TOS            A - "top of stack"
;RSP            zp pointer (8) - return stack pointer
;IPBASE         zp pointer (16)
;IP             VM_IP - the lsb is "cached" in Y for speed.
;A              zp pointer (16) - auto incrementing src/dest address or temp storage for routines.
;B              zp pointer (16) - volatile general purpose src/dest address.  no direct access opcodes.
;O              "object" register used in conjuction with literals to work with interleaved objects

;6502 X is free at the asm level but destroyed by VMNEXT
;Y is also somewhat free, you just are required to restore it with a LDY VM_IP when you're done.

VM_BTM =$00
VM_TOP =$30

ENUM VM_BTM
	VM_RSP:  .db 0	   ; 8-bit return stack pointer
	VM_SPTMP:.db 0
	VM_TEMP: .dw 0    ; work variable
	VM_A:    .dw 0
	VM_B:    .dw 0
	VM_O:		.dw 0    ; high byte is kept 0
	VM_RTOP = VM_TOP - VMNEXT_SIZE ; grows downward, pre-decremented.
	VM_NEXT = (VM_TOP - VMNEXT_SIZE) + 1
   VM_IP = VM_NEXT + 7   ; instruction pointer, pre-incremented.
   VM_CURRENT = VM_IP - 1 ;  jump without incrementing
ENDE

macro init_vm
	; copy inner loop to ZP
	ldx #VMNEXT_SIZE-1
-  lda _vmnext,x
	sta VM_NEXT-1,x
	dex
	bpl -
endm

macro vmnext
	jmp VM_NEXT
	align 2
endm

macro vmcurrent
	jmp VM_CURRENT
	align 2
endm

macro vmnext_pla
	jmp VM_NEXT-1
	align 2
endm

; could be used to implement single-instruction loops
;macro vmcurrent
;	jmp VM_CURRENT
;endm


macro ipplus
	iny         ;2
	iny         ;2
	sty VM_IP   ;3
	bne +       ;3
	inc VM_IP+1
+
endm

; byte literals must be padded to maintain alignment (use .dw and it is taken care of)
macro ifetch   ;20
	ipplus
	ldy #0           ;2
	lda (VM_IP),y    ;5
	ldy VM_IP        ;3
endm



; ------------- core opcodes -------------

; to compile opcodes, .dw label

end:	     ; use to get out of interpreter  (return and data stack irrelevant!)
	ldx   VM_SPTMP
	txs
	rts

;38
enter:  ; goes at the top of routines where you want to use threaded code (handled by the "colon" macro)

	; push ip onto return stack
	ldx VM_RSP
	dex
	dex
	stx VM_RSP
	sty 0,x      ;low byte of VM_IP should be in Y
	ldy VM_IP+1
 	sty 1,x

	; fall into ...

interpret:
	; set new IP - pull address, which is our destination - 2
	;   because of the 2-byte alignment requirement, we should assume that the next byte after the jsr to enter
	;   is a 0, and the following byte is the first XT
   ;   because IP is PREINCREMENT, and jsr does not increment the PC past its 3rd byte,
	;   we don't need to modify the pulled address at all.

	tax

	pla  ;low
	tay  ;cache the low byte in Y!  it gets stored in VM_IP in vmnext.
	pla  ;high
	sta VM_IP+1

	txa

	vmnext



execute:  ; addrh/l --    address - 2
	pha
	rts

exit:
	ldx VM_RSP
	ldy 1,x
	sty VM_IP+1
   ldy 0,x
	inx
	inx
	stx VM_RSP
	vmnext

twolit:
	pha
droptwolit:
	;38
	ldy #3			;2
	lda (VM_IP),y	;5
	pha				;3
	dey				;2
	lda (VM_IP),y	;5
	tax				;2

	lda VM_IP		;3
	clc				;2
	adc #4			;2
	sta VM_IP		;3
	bcs +inciph 	;2
	tay				;2

	txa				;2
	vmcurrent		;8
lit:
  	pha
droplit:
	;33
	ldy #2			;2
	lda (VM_IP),y	;5
	tax				;2

	lda VM_IP		;3
	clc				;2
	adc #4			;2
	sta VM_IP		;3
	bcs +inciph		;2
	tay				;2
	txa				;2
	vmcurrent		;8
+inciph
	inc VM_IP+1    ;5
	tay				;2
	txa				;2
	vmcurrent		;8

;40
;  	ifetch
;  	vmnext

fetch:
   tax
  	lda  0,x
  	vmnext

store:
  	tax
  	pla
	sta  0,x
drop:
	vmnext_pla

plusstore:
	tax
	pla
	clc
	adc  0,x
	sta  0,x
  	vmnext_pla

plus:
	sta VM_TEMP   ;3
	pla           ;4
	clc           ;2
	adc VM_TEMP   ;3 = 12
	vmnext

minus:
	sta VM_TEMP
	pla
	sec
	sbc VM_TEMP
	vmnext

andd:
	sta VM_TEMP
	pla
	and VM_TEMP
	vmnext

dropdup:
	pla
dup:
	pha
	vmnext

twodup:
   pha
droptwodup:
	tsx
	lda $102,x
over:
   pha
dropover:
	tsx
	lda $102,x
	vmnext

nip:
	tax
	pla
	txa
	vmnext

eightdiv:
	CMP	#$80
	ROR	A
fourdiv:
	CMP	#$80
	ROR	A
twodiv:
	CMP	#$80
	ROR	A
	vmnext

eightmul:
	asl   a
fourmul:
	asl   a
twomul:
	asl   a
	vmnext

astore:
	sta VM_A
	pla
	sta VM_A+1
  	vmnext_pla


r:
	pha
dropr:
	ldx VM_RSP
	lda 0,x
	vmnext


iff:
	tax
	beq	branch
	bne   nobranch

ifback:
	tax
	beq	branchback
	bne   nobranch



nif:
	tax
	bne   branch
	beq   nobranch


next:
	sta VM_TEMP
dropnext:
	ldx	VM_RSP
	lda	0,x
	beq   endfor
	dec	0,x
; back only
nextbranch:
;29
	tya				;2
	ldy #2			;2
	sec				;2
	sbc (VM_IP),y	;5
	tay				;2
	sta VM_IP		;3
	bcc +				;2
	lda VM_TEMP    ;3
	vmcurrent		;8
+
	dec VM_IP+1
   lda VM_TEMP
	vmcurrent
endfor:
	lda VM_TEMP

	inc	VM_RSP
	iny
	iny
	beq +
  	vmnext
+
	inc	VM_IP+1
  	vmnext



nobranch:
	iny
	iny
	beq +
  	vmnext_pla
+
	inc	VM_IP+1
  	vmnext_pla


; forward only
branch:
;30
	tya				;2
	ldy #2			;2
	clc				;2
	adc (VM_IP),y	;5
	tay				;2
	sta VM_IP		;3
	bcs +				;2
	pla            ;4
	vmcurrent		;8
+
	inc VM_IP+1
	pla
	vmcurrent

; back only
branchback:
	tya				;2
	ldy #2			;2
	sec				;2
	sbc (VM_IP),y	;5
	tay				;2
	sta VM_IP		;3
	bcc +				;2
	pla
	vmcurrent		;8
+
	dec VM_IP+1
	pla
	vmcurrent



dplus:
	tsx
	clc
	adc $102,x
	sta $102,x
	lda $103,x
	adc $101,x
	sta $103,x
twodrop:
	pla
  	vmnext_pla

false:
zero:
	pha
dropzero:
	lda	#0
   vmnext

true:
	pha
droptrue:
	lda	#$FF
	vmnext

fetchplus:  ;3+6+2+3+2+3
	pha
dropfetchplus:
	ldx	#0
	lda	(VM_A,x)
aplus:
	inc	VM_A
	beq +         ; save a cycle by not taking the branch 99.53% of the time
	vmnext
+
	inc	VM_A+1
	vmnext

storeplus:
	ldx	#0
	sta	(VM_A,x)
	inc	VM_A
	beq +         ; save a cycle by not taking the branch 99.53% of the time
	vmnext_pla
+
	inc	VM_A+1
	vmnext_pla



; * v * ====== THE INNER LOOP ====== ^ _ ~

_vmnext:
	pla  ; for size optimization of some routines.  most opcodes jump to the byte after this.

	iny			 	;2
	iny				;2
	sty VM_IP		;3
	beq +          ;2

VM_IP = VM_NEXT + ($-_vmnext)
	jmp ($0000)    ;5
+
	inc VM_IP+1
   jmp VM_IP-1

	VMNEXT_SIZE = $ - _vmnext


; * v * ====== THE ENTRY POINT ====== ^ _ ~

; entry point!   use the "script" macro which calls this and pads the JSR
; it's required that we pad the jsr to maintain alignment.
bytecode:
	tsx
	inx
	inx
	stx   VM_SPTMP      ; this allows us to safely get out of bytecode at any point.
	ldx   #VM_RTOP   ; initialize RSP
	stx   VM_RSP
	jmp   interpret


; pop return address
;  useful for inline data or callbacks
popra: ; -- addrh/l
	pha
	ldx VM_RSP
	lda 1,x
	pha
	lda 0,x
	inx
	inx
	stx VM_RSP
	vmnext

;  asm equivalent - jsr to a label and have this be the first line of code.
;   the caller can put data or more code (bytecode or asm) underneath the jsr.
;   warning: doesn't work across page boundary (so don't put this at $FC)
macro asmpopra
	pla  ; low
	clc
	adc #1
	; high is already on stack
endm

for:
	dec	VM_RSP
	ldx	VM_RSP
	sta	0,x
	dec   0,x
	vmnext_pla

equals:
	sta	VM_TEMP
	pla
	cmp 	VM_TEMP
	bne	+
	lda	#$ff
	vmnext
+	lda	#$00
	vmnext


zerogreater:
	pha
	lda	#0
greater:
	sta	VM_TEMP
	pla
	cmp 	VM_TEMP   ; compare tos to 2os
	beq	++
	bpl 	+
++	lda	#$00
	vmnext
+	lda	#$ff
	vmnext

zeroless:
	pha
	lda	#0
less:
	sta	VM_TEMP
	pla
	cmp 	VM_TEMP
	bmi	+
	lda	#$00
	vmnext
+	lda	#$ff
	vmnext


uless:
	sta	VM_TEMP
	pla
	cmp 	VM_TEMP
	bcs	+
	lda	#$00
	vmnext
+	lda	#$ff
   vmnext

ugreater:
	sta	VM_TEMP
	pla
	cmp 	VM_TEMP
	beq	++
	bcs	+
++	lda	#$00
	vmnext
+	lda	#$ff
	vmnext


swap:
	tax
	pla
	sta VM_TEMP
	txa
	pha
	lda VM_TEMP
	vmnext

; quickly move up to 256 bytes of data.  A is A+count after.
move:  ; A=src ... desth/l count8 --   A=A+count   B=dest
	ldy #0
	tax
	pla
	sta VM_B
	pla
	sta VM_B+1
-      LDA (VM_A),Y ; move the remaining bytes
       STA (VM_B),Y
       INY
       DEX
       BNE -
   ; add transfer count to A.
	tya
	clc
	adc VM_A
	bne +
	inc VM_A+1
+
	sta VM_A

	;restore pc and tos
	ldy VM_IP
   vmnext_pla


afetch:
	pha
	lda VM_A+1
	pha
	lda VM_A
	vmnext

ostore:
	sta VM_O
	vmnext_pla

; not currently used to due to ridiculous slowness.  better to make custom routines for each property and/or
;  use some properties only within asm routines.
fetcho:
	pha
	ifetch
	tay
	lda	(VM_O),y
	ldy	VM_IP
	vmnext

storeo:
	tax
	ifetch
	tay
	txa
	sta	(VM_O),y
	ldy	VM_IP
	vmnext_pla

xor:
	sta VM_TEMP
	pla
	eor VM_TEMP
	vmnext

or:
	sta VM_TEMP
	pla
	ora VM_TEMP
	vmnext


oplus:
	inc VM_O
	vmnext

include eighth\macros_dtc.s
